
/**
 * AppleIIGo
 * GCR 6&2 Disk Image class
 * (C) 2008 by Marc S. Ressl (mressl@umich.edu)
 * Released under the GPL
 */

#include "GCR62DiskImage.h"
#include "FDIDiskImage.h"

UINT8 gcr62SectorMap[][GCR62_TRACK_SECTORNUM] = {
	{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15},	
	{0, 7, 14, 6, 13, 5, 12, 4, 11, 3, 10, 2, 9, 1, 8, 15},
	{0, 8, 1, 9, 2, 10, 3, 11, 4, 12, 5, 13, 6, 14, 7, 15},
	{0, 11, 6, 1, 12, 7, 2, 13, 8, 3, 14, 9, 4, 15, 10, 5},
};

bool GCR62DiskImage::isDiskImageValid(UINT32 diskType,
									  UINT32 diskImageByteNum) {
	if (diskType != FLOPPYDISKTYPE_5_25)
		return false;
	
	for (UINT32 diskImageTrackNum = 35;
		 diskImageTrackNum <= 40;
		 diskImageTrackNum++)
		if (diskImageByteNum == (diskImageTrackNum * GCR62_TRACK_BYTENUM))
			return true;
	
	return false;
}

GCR62DiskImage::GCR62DiskImage(char * diskImagePath,
							   UINT32 diskImageOffset,
							   UINT32 diskImageByteNum,
							   UINT32 gcrSectorOrder,
							   UINT8 gcrVolume,
							   bool isWriteProtectionForced) :
GCRDiskImage(FLOPPYDISKTYPE_5_25, diskImagePath, GCRTYPE_6_2) {
	if (!isDiskImageOpened())
		return;
	
	this->diskImageOffset = diskImageOffset;
	
	if (gcrSectorOrder >= GCR62_SECTORORDER_END)
		gcrSectorOrder = GCR62_SECTORORDER_IMG;
	this->gcrSectorOrder = gcrSectorOrder;
	this->gcrVolume = gcrVolume;
	diskImageTrackNum = diskImageByteNum / GCR62_TRACK_BYTENUM;
	
	if (!allocateDisk())
		return;
	
	if (!readDiskImage(diskImageOffset, diskImageTrackNum))
		return;
	
	setWriteProtected(isWriteProtected() || isWriteProtectionForced);
}

GCR62DiskImage::~GCR62DiskImage() {
	if (!isDiskImageOpened())
		return;
	
	if (!isModified())
		return;
	
	if (!writeDiskImage(diskImageOffset, false))
		FDIDiskImage::writeDiskImage(this);
	else
		writeDiskImage(diskImageOffset, true);
}

void GCR62DiskImage::getDiskTrack(UINT32 * trackIndex, UINT32 * headIndex, 
								  UINT32 * diskImageTrackByteNum,
								  UINT32 diskImageTrackIndex) {
	*trackIndex = diskImageTrackIndex * 4;
	*headIndex = 0;
	*diskImageTrackByteNum = GCR62_TRACK_BYTENUM;
}

void GCR62DiskImage::getDiskImageTrack(UINT32 * diskImageTrackIndex,
									   UINT32 * diskImageTrackByteNum,
									   UINT32 trackIndex, UINT32 headIndex) {
	*diskImageTrackIndex = trackIndex / 4;
	if (headIndex || (trackIndex & 0x3) ||
		(*diskImageTrackIndex >= diskImageTrackNum))
		*diskImageTrackByteNum = 0;
	else
		*diskImageTrackByteNum = GCR62_TRACK_BYTENUM;
}

void GCR62DiskImage::writeAddressField(UINT32 diskImageTrackIndex,
									   UINT32 sectorIndex) {
	UINT32 gcrTrackIndex = diskImageTrackIndex;
	
	writeNibble(0xd5);
	writeNibble(0xaa);
	writeNibble(0x96);
	
	writeFMValue(gcrVolume);
	writeFMValue(gcrTrackIndex);
	writeFMValue(sectorIndex);
	writeFMValue(gcrVolume ^ gcrTrackIndex ^ sectorIndex);
	
	writeNibble(0xde);
	writeNibble(0xaa);
	writeNibble(0xeb, 4);
}

void GCR62DiskImage::writeDataField(UINT8 * sectorData) {
	UINT8 swapBit01[] = {0x00, 0x02, 0x01, 0x03};
	UINT8 swapBit23[] = {0x00, 0x08, 0x04, 0x0c};
	UINT8 swapBit45[] = {0x00, 0x20, 0x10, 0x30};
	
	writeNibble(0xd5);
	writeNibble(0xaa);
	writeNibble(0xad);
	
	initGcrChecksum();
	
	for (UINT32 index = 0; index < 0x56; index++)
		writeGcrValue(swapBit01[sectorData[0x00 + index] & 0x3] |
					  swapBit23[sectorData[0x56 + index] & 0x3] |
					  ((index > 0x53) ? 0 :
					   swapBit45[sectorData[0xac + index] & 0x3]));
	
	for (UINT32 index = 0; index < GCR62_SECTOR_BYTENUM; index++)
		writeGcrValue(sectorData[index] >> 2);
	
	writeGcrChecksum();
	
	writeNibble(0xde);
	writeNibble(0xaa);
	writeNibble(0xeb);
}

void GCR62DiskImage::writeTrack(UINT32 diskImageTrackIndex,
								UINT8 * diskImageTrackData) {
	writeSync(0x80, 10, 8);
	
	for (UINT32 index = 0; index < GCR62_TRACK_SECTORNUM; index++) {
		if (index)
			writeSync(0x14, 10, 8);
		writeAddressField(diskImageTrackIndex, index);
		writeSync(0x08, 10, 9);
		writeDataField(&diskImageTrackData[GCR62_SECTOR_BYTENUM *
			gcr62SectorMap[gcrSectorOrder][index]]);
	}
}

bool GCR62DiskImage::readAddressField(UINT32 diskImageTrackIndex,
									UINT32 sectorIndex) {
	UINT32 gcrTrackIndex = diskImageTrackIndex;
	
	for (UINT32 index = 0; index < ((DISK_5_25_TRACK_BITNUM * 2) / 8);
		 index++) {
		if (readNibble() != 0xd5)
			continue;
		if (readNibble() != 0xaa)
			continue;
		if (readNibble() != 0x96)
			continue;
		
		UINT32 readGcrVolume = readFMValue();
		UINT32 readGcrTrackIndex = readFMValue();
		UINT32 readSectorIndex = readFMValue();
		UINT32 checksum = readFMValue();
		if (checksum != (readGcrVolume ^ readGcrTrackIndex ^ readSectorIndex))
			continue;
		if (gcrTrackIndex != readGcrTrackIndex)
			continue;
		if (sectorIndex != readSectorIndex)
			continue;
		
		if (readNibble() != 0xde)
			continue;
		if (readNibble() != 0xaa)
			continue;
		
		return true;
	}
	
	return false;
}

bool GCR62DiskImage::readDataField(UINT8 * sectorData) {
	UINT8 swapBit01[] = {0x00, 0x02, 0x01, 0x03};
	
	for (UINT32 index = 0; index < 12; index++) {
		if (readNibble() != 0xd5)
			continue;
		if (readNibble() != 0xaa)
			continue;
		if (readNibble() != 0xad)
			continue;
		
		initGcrChecksum();
		
		for (UINT32 index = 0; index < 0x56; index++) {
			UINT8 value = readGcrValue();
			sectorData[0x00 + index] = swapBit01[(value >> 0) & 0x3];
			sectorData[0x56 + index] = swapBit01[(value >> 2) & 0x3];
			if (index <= 0x53)
				sectorData[0xac + index] = swapBit01[(value >> 4) & 0x3];
		}
		
		for (UINT32 index = 0; index < GCR62_SECTOR_BYTENUM; index++)
			sectorData[index] |= readGcrValue() << 2;
		
		if (!checkGcrChecksum())
			return false;
		
		if (readNibble() != 0xde)
			return false;
		if (readNibble() != 0xaa)
			return false;
		
		return true;
	}
	
	return false;
}

bool GCR62DiskImage::readTrack(UINT32 diskImageTrackIndex,
							   UINT8 * diskImageTrackData) {
	if (getNibbleNum() < (GCR62_TRACK_BYTENUM * 8 / 6))
		return false;
	
	for (UINT32 index = 0; index < GCR62_TRACK_SECTORNUM; index++) {
		if (!readAddressField(diskImageTrackIndex, index))
			return false;
		if (!readDataField(&diskImageTrackData[GCR62_SECTOR_BYTENUM * 
			gcr62SectorMap[gcrSectorOrder][index]]))
			return false;
	}
	
	return true;
}
